﻿using MvvmCross.Core.ViewModels;
using MvvmCross.Plugins.Messenger;
using Sidvall.Serialization;
using Sidvall.UI.Messaging;
using Sidvall.UI.Navigation;
using Sidvall.UI.Views;
using System.Collections.Generic;
using System.Threading.Tasks;

namespace Sidvall.UI.ViewModels
{
    public abstract class ViewModelBase : MvxViewModel, IViewModel, IBindingContext
    {
        #region Public Members

        public Sidvall.UI.Views.IView ViewModelView { get; set; }
        public string ViewModelRegionName { get; set; }

        #region IsBusy

        private bool _IsBusy;
        public bool IsBusy
        {
            get
            {
                return _IsBusy;
            }
            set
            {
                SetIsBusy(value, null, null);
            }
        }

        #endregion
        #region IsBusyMessage

        private string _IsBusyMessage;
        public string IsBusyMessage
        {
            get
            {
                return _IsBusyMessage;
            }
            set
            {
                if (_IsBusyMessage == value)
                    return;
                _IsBusyMessage = value;
                RaisePropertyChanged(nameof(IsBusyMessage));
            }
        }

        #endregion
        #region IsBusyScope

        private string _IsBusyScope;
        public string IsBusyScope
        {
            get
            {
                return _IsBusyScope;
            }
            set
            {
                if (_IsBusyScope == value)
                    return;
                _IsBusyScope = value;
                RaisePropertyChanged(nameof(IsBusyScope));
            }
        }

        #endregion
        #region ViewModelControls

        private volatile ViewModelControlCollection _ViewModelControls;
        public ViewModelControlCollection ViewModelControls
        {
            get
            {
                if (_ViewModelControls == null)
                {
                    lock (_Mutex)
                    {
                        if (_ViewModelControls == null)
                        {
                            var items = new ViewModelControlCollection();
                            items.IsBusyChanged += OnViewModelControlsIsBusyChanged;
                            _ViewModelControls = items;
                        }
                    }
                }
                return _ViewModelControls;
            }
        }

        #endregion

        #region Start

        public async override void Start()
        {
            try
            {
                using (var manager = new Sidvall.Data.BusyManager(this, "Starting...", "ViewModelBase.Start"))
                {
                    var task = StartAsync();
                    if (task != null)
                    {
                        await task;
                    }
                }
            }
            catch (System.Exception ex)
            {
                await LogAsync(ex);
                await Sidvall.UI.SystemContext.Current.DialogManager.ShowDialogErrorMessageAsync(ex);
            }
            OnAfterStart();
            await Sidvall.UI.Views.ViewManager.NotifyViewAsync(Views.ViewEventType.ViewModelStarted, this);
        }

        #endregion

        // Protected
        #region ViewModelHistoryRegionItem

        protected HistoryRegionItem ViewModelHistoryRegionItem
        {
            get
            {
                if (string.IsNullOrWhiteSpace(this.ViewModelRegionName))
                    return null;
                if (Sidvall.UI.SystemContext.Current.NavigationManager == null)
                    return null;
                if (Sidvall.UI.SystemContext.Current.NavigationManager.CurrentHistoryItem == null)
                    return null;
                return Sidvall.UI.SystemContext.Current.NavigationManager.CurrentHistoryItem.RegionItems.GetItem(this.ViewModelRegionName);
            }
        }

        #endregion
        #region ViewModelResources

        private System.Lazy<ViewModelResourceCollection> _ViewModelResources = new System.Lazy<ViewModelResourceCollection>();
        protected ViewModelResourceCollection ViewModelResources
        {
            get
            {
                return _ViewModelResources.Value;
            }
        }

        #endregion

        #region ChangePresentation

        protected void ChangePresentation(ViewModels.ViewModelActionType type)
        {
            ChangePresentation(type, null);
        }
        protected void ChangePresentation(ViewModels.ViewModelActionType type, string context)
        {
            PresentationHint hint;

            hint = new PresentationHint()
            {
                ActionType = type,
                Context = context,
            };
            ChangePresentation(hint);
        }

        #endregion
        #region OnAfterStart

        protected virtual void OnAfterStart()
        {
        }

        #endregion
        #region OnCultureChanged

        protected virtual void OnCultureChanged(CultureMessage item)
        {
        }

        #endregion
        #region OnIsBusyChanged

        protected virtual void OnIsBusyChanged()
        {
        }
        protected virtual void OnIsBusyChanged(bool isBusy, string message, string scope)
        {
            OnIsBusyChanged();
            OnViewModelEvent(ViewModelMessage.CreateIsBusyMessage(this, this.IsBusy, message, scope));
        }

        #endregion
        #region OnViewModelEvent

        protected virtual void OnViewModelEvent(ViewModelMessage message)
        {
            EventManager.Publish(message);
        }

        #endregion
        #region OnViewEventAsync

        protected virtual Task OnViewEventAsync(Sidvall.UI.Views.ViewEventArgs args)
        {
            if (args == null)
                throw new System.ArgumentNullException(nameof(args));
            switch (args.EventType)
            {
                case Sidvall.UI.Views.ViewEventType.ViewUnloaded:
                    UnloadViewModel();
                    break;
                default:
                    break;
            }
            return Task.FromResult(true);
        }

        #endregion
        #region Publish

        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic")]
        protected void Publish<TMessage>(TMessage message) where TMessage : MvxMessage
        {
            EventManager.Publish(message);
        }

        #endregion
        #region SetIsBusy

        protected void SetIsBusy(bool isBusy, string message, string scope)
        {
            if (isBusy)
            {
                if (!_IsBusy)
                {
                    _IsBusy = true;
                    RaisePropertyChanged(nameof(IsBusy));
                    this.IsBusyMessage = message;
                    this.IsBusyScope = scope;
                    OnIsBusyChanged(isBusy, message, scope);
                }
            }
            else if (_IsBusy)
            {
                if (this.IsBusyScope.Equals(scope, true, System.StringComparison.OrdinalIgnoreCase))
                {
                    _IsBusy = false;
                    RaisePropertyChanged(nameof(IsBusy));
                    this.IsBusyMessage = null;
                    this.IsBusyScope = null;
                    OnIsBusyChanged(isBusy, message, scope);
                }
            }
        }

        #endregion
        #region ShowViewModel

        protected void ShowViewModel(System.Type viewModelType, object parameterValues, bool addToHistory)
        {
            ShowViewModel(viewModelType, parameterValues, PresentationManager.GetPresentationBundle(null, addToHistory));
        }
        protected void ShowViewModel(System.Type viewModelType, IMvxBundle parameterBundle, bool addToHistory)
        {
            ShowViewModel(viewModelType, parameterBundle, PresentationManager.GetPresentationBundle(null, addToHistory));
        }
        protected void ShowViewModel(System.Type viewModelType, IDictionary<string, string> parameterValues, bool addToHistory)
        {
            ShowViewModel(viewModelType, parameterValues, PresentationManager.GetPresentationBundle(null, addToHistory));
        }
        protected void ShowViewModel(System.Type viewModelType, object parameterValues, bool addToHistory, string childRegionName)
        {
            ShowViewModel(viewModelType, parameterValues, PresentationManager.GetPresentationBundle(childRegionName, addToHistory));
        }
        protected void ShowViewModel(System.Type viewModelType, IMvxBundle parameterBundle, bool addToHistory, string childRegionName)
        {
            ShowViewModel(viewModelType, parameterBundle, PresentationManager.GetPresentationBundle(childRegionName, addToHistory));
        }
        protected void ShowViewModel(System.Type viewModelType, IDictionary<string, string> parameterValues, bool addToHistory, string childRegionName)
        {
            ShowViewModel(viewModelType, parameterValues, PresentationManager.GetPresentationBundle(childRegionName, addToHistory));
        }

        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter")]
        protected void ShowViewModel<TViewModel>(object parameterValues, bool addToHistory) where TViewModel : IMvxViewModel
        {
            ShowViewModel<TViewModel>(parameterValues, PresentationManager.GetPresentationBundle(null, addToHistory));
        }
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter")]
        protected void ShowViewModel<TViewModel>(IMvxBundle parameterBundle, bool addToHistory) where TViewModel : IMvxViewModel
        {
            ShowViewModel<TViewModel>(parameterBundle, PresentationManager.GetPresentationBundle(null, addToHistory));
        }
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter")]
        protected void ShowViewModel<TViewModel>(IDictionary<string, string> parameterValues, bool addToHistory) where TViewModel : IMvxViewModel
        {
            ShowViewModel<TViewModel>(parameterValues, PresentationManager.GetPresentationBundle(null, addToHistory));
        }
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter")]
        protected void ShowViewModel<TViewModel>(object parameterValues, bool addToHistory, string childRegionName) where TViewModel : IMvxViewModel
        {
            ShowViewModel<TViewModel>(parameterValues, PresentationManager.GetPresentationBundle(childRegionName, addToHistory));
        }
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter")]
        protected void ShowViewModel<TViewModel>(IMvxBundle parameterBundle, bool addToHistory, string childRegionName) where TViewModel : IMvxViewModel
        {
            ShowViewModel<TViewModel>(parameterBundle, PresentationManager.GetPresentationBundle(childRegionName, addToHistory));
        }
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter")]
        protected void ShowViewModel<TViewModel>(IDictionary<string, string> parameterValues, bool addToHistory, string childRegionName) where TViewModel : IMvxViewModel
        {
            ShowViewModel<TViewModel>(parameterValues, PresentationManager.GetPresentationBundle(childRegionName, addToHistory));
        }

        #endregion
        #region StartAsync

        protected virtual Task StartAsync()
        {
            return null;
        }

        #endregion
        #region Subscribe

        protected void Subscribe<TMessage>(System.Action<TMessage> deliveryAction) where TMessage : MvxMessage
        {
            this.ViewModelResources.Add(EventManager.Subscribe(deliveryAction));
        }
        protected void Subscribe<TMessage1, TMessage2>(System.Action<TMessage1> deliveryAction1, System.Action<TMessage2> deliveryAction2)
            where TMessage1 : MvxMessage
            where TMessage2 : MvxMessage
        {
            this.ViewModelResources.AddRange(EventManager.Subscribe(deliveryAction1, deliveryAction2));
        }
        protected void Subscribe<TMessage1, TMessage2, TMessage3>(System.Action<TMessage1> deliveryAction1, System.Action<TMessage2> deliveryAction2,
            System.Action<TMessage3> deliveryAction3)
            where TMessage1 : MvxMessage
            where TMessage2 : MvxMessage
            where TMessage3 : MvxMessage
        {
            this.ViewModelResources.AddRange(EventManager.Subscribe(deliveryAction1, deliveryAction2, deliveryAction3));
        }
        protected void Subscribe<TMessage1, TMessage2, TMessage3, TMessage4>(System.Action<TMessage1> deliveryAction1, System.Action<TMessage2> deliveryAction2,
            System.Action<TMessage3> deliveryAction3, System.Action<TMessage4> deliveryAction4)
            where TMessage1 : MvxMessage
            where TMessage2 : MvxMessage
            where TMessage3 : MvxMessage
            where TMessage4 : MvxMessage
        {
            this.ViewModelResources.AddRange(EventManager.Subscribe(deliveryAction1, deliveryAction2, deliveryAction3, deliveryAction4));
        }

        #endregion
        #region UnloadViewModel

        protected virtual void UnloadViewModel()
        {
            this.ViewModelView = null;
            if (_ViewModelControls != null)
            {
                _ViewModelControls.IsBusyChanged -= OnViewModelControlsIsBusyChanged;
                _ViewModelControls.Clear();
                _ViewModelControls = null;
            }
            var iMax = this.ViewModelResources.Count;
            for (int i = iMax - 1; i >= 0; i--)
            {
                var viewModelResource = this.ViewModelResources[i];
                if (viewModelResource != null)
                    viewModelResource.Dispose();
            }
            this.ViewModelResources.Clear();
        }

        #endregion
        #region OnViewModelControlsIsBusyChanged

        protected virtual void OnViewModelControlsIsBusyChanged(object sender, BusyChangedEventArgs e)
        {
            if (e == null)
                throw new System.ArgumentNullException(nameof(e));
            OnViewModelEvent(ViewModelMessage.CreateIsBusyMessage(sender, e.IsBusy, e.Message, e.Scope));
        }

        #endregion

        #region IBusyProvider Members

        void Sidvall.Data.IBusyProvider.SetIsBusy(bool isBusy, string message, string scope)
        {
            SetIsBusy(isBusy, message, scope);
        }

        #endregion
        #region IViewModel Members

        async Task IViewModel.OnViewEventAsync(ViewEventArgs args)
        {
            await OnViewEventAsync(args);
        }

        #endregion

        #endregion
        #region Private Members

        private readonly object _Mutex = new object();

        #region LogManager

        private Sidvall.Logging.LogManager LogManager
        {
            get
            {
                return Sidvall.SystemContext.Current.LogManager;
            }
        }

        #endregion

        #region LogAsync

        private async Task LogAsync(System.Exception exception)
        {
            if (this.LogManager != null)
                await this.LogManager.WriteAsync(exception, Sidvall.Logging.LogCategories.Application);
        }

        #endregion
        #region OnCultureChangedInternal

        private async void OnCultureChangedInternal(CultureMessage item)
        {
            try
            {
                OnCultureChanged(item);
            }
            catch (System.Exception ex)
            {
                await LogAsync(ex);
                await Sidvall.UI.SystemContext.Current.DialogManager.ShowDialogErrorMessageAsync(ex);
            }
        }

        #endregion

        #endregion
        #region Constructors

        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
        protected ViewModelBase()
        {
            try
            {
                Subscribe<CultureMessage>(OnCultureChangedInternal);
            }
            catch
            {
            }
        }

        #endregion
    }
}
